
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Diagnostics;
using System.Collections;
using System.Xml;

using Swaf;
using Swaf.Call;
using Swaf.Config;

namespace Swaf.BizRule
{
	/// <summary>
	/// The main class for calling business rules for the application that is conceptually
	/// both a <i>facade</i> and a <i>proxy</i> for clients to execute business rules
	/// that supports both synchronous and asynchronous calls.</summary>
	/// <author>mdenman</author>
	public class RuleManager : BasicExecutor, IRuleManager, IConfigPartner, IRuleManagerEx
	{
		private static string s_executeRuleName = "executeBizRules";
		private static IClassFactory s_ruleDecoratorFactory = null;
		private const string SECURITY_DECORATOR = "security";

		public static IClassFactory RuleDecoratorFactory
		{
			get
			{
				if(s_ruleDecoratorFactory == null)
					s_ruleDecoratorFactory = Application.currentApp.factories["BizRuleDecorators"];
				return s_ruleDecoratorFactory;
			}
		}
		public virtual void registerForConfig(IConfigManager config)
		{
			{
				s_ruleQueue = null;
				s_ruleDecoratorFactory = null;
				s_isStartup=true;	
				s_ruleInfo = new XmlDocument();
				s_ruleInfo.LoadXml("<RuleTypeInfo/>");
				s_rules = null;
				s_startupRules = null;
				s_dynamicRuleRegister=null;

				config.registerHandler("...<ThreadingConfig> <CompositeMonitorClass>", new CompositeMonitorConfigHandler(this));
				config.registerHandler("...<ThreadingConfig> <RuleContextClass>", new RuleContextConfigHandler(this));
				config.registerHandler("...<ThreadingConfig> <RuleQueueClass>", new AnonymousClassConfigHandler(this));
				config.registerHandler("...<BusinessRules>", new RuleGroupConfigHandler(this));
				config.registerHandler("...<BusinessRule>", new AnonymousClassConfigHandler1(this));
				config.registerHandler("...<DynamicRuleRegister>", new DynamicRuleRegistrationHandler(this));
			}
		}

		public void checkConfigStatus()
		{
			s_isStartup=false;
			if(s_ruleQueue != null)
				s_ruleQueue.Monitor = m_monitors;
			else
				throw new BadConfigException("No Rule queue was defined or able to be created for the rule manager.  The system cannot start until it can be created.  Ensure, that the <ThreadingConfig><RuleQueueClass> element is specified in the configuration.");

			IClassFactory ruleTypeFactory = m_app.factories["CallTypes"];
			XmlDocument doc = new XmlDocument();
			doc.LoadXml("<LocalClass name='Swaf.BizRule.CallRulesBizRule,SwafCore' />");
			//rules[s_executeRuleName] = 
			BasicRuleInfo info =	new BasicRuleInfo((ICall)ruleTypeFactory.createFromNickName("LocalClass", s_executeRuleName, doc.DocumentElement), s_executeRuleName, CallDistanceType.inDomain);
			addRuleDef(s_executeRuleName, info, rules);
		}
		internal ICall dynamicRuleRegister
		{
			get{return s_dynamicRuleRegister;}
			set{s_dynamicRuleRegister=value;}
		}
		public string RuleInfoXML
		{
			get
			{
				return s_ruleInfo.OuterXml;
			}
			
		}
		
		/// <summary>Creates a new instance of RuleManager 
		/// </summary>
		public RuleManager(IApplication app)
		{
			m_app = app;
		}
		

		/// <summary>
		/// Add a rule monitor to listen for rule execution events as the <c>RuleManager</c>
		/// executes rules and specific events occur during execution.</summary>
		/// <param name="monitor"></param>
		public virtual void  addMonitor(IRuleMonitor monitor)
		{
			Debug.Assert(m_monitors != null);
			m_monitors.addMonitor(monitor);
		}
		
		/// <summary>
		/// Removes the rule monitor from listening to rule execution events.</summary>
		/// <param name="monitor"></param>
		public virtual void  removeMonitor(IRuleMonitor monitor)
		{
			Debug.Assert(m_monitors != null);
			m_monitors.removeMonitor(monitor);
		}
		
		/// <summary>
		/// Stops rule processing and shutsdown its <c>IRuleQueue</c> object, which means that
		/// no more rules will be executed via calls to <c>execute</c>.</summary>
		/// <remarks>
		/// This method should only be called when the application using the <c>RuleManager</c>
		/// is shutting down.</remarks>
		public void stop(IApplication app)
		{
			s_ruleQueue.stop();
		}

		public uint WorkerCount 
		{
			get
			{
				return s_ruleQueue.WorkerCount;
			}
			set
			{
				s_ruleQueue.WorkerCount = value;
			}
		}

		protected ICompositeMonitor m_monitors;
				
		protected IApplication m_app;
		internal void defineQueueClass(string name, XmlElement el)
		{
			s_ruleQueue = (IRuleQueue)Cmn.callCtor(name, new Type[]{typeof(XmlElement)}, new object[]{el});
		}

		protected override IApplication App {get{return m_app;}}
		
		internal void defineBusinessRule(string ruleName, XmlElement typeInfo, IList decoratorNameValuePairs)
		{
			IClassFactory ruleTypeFactory = m_app.factories["CallTypes"];
			if(typeInfo != null)
			{
				typeInfo = (XmlElement)s_ruleInfo.ImportNode(typeInfo,true);
				s_ruleInfo.DocumentElement.AppendChild(typeInfo);
			}
			try
			{
				ICall callInfo = null;
				if(typeInfo != null)
					callInfo = (ICall)ruleTypeFactory.createFromNickName(typeInfo.Name, ruleName, typeInfo);
				else
					callInfo = new DisabledCall();

				CallDistanceType distance = CallDistanceType.unknown;
				BasicRuleInfo ruleInfo = new BasicRuleInfo(callInfo, ruleName, distance);
				foreach(string[] decoratorPair in decoratorNameValuePairs)
				{
					IDecorator d = (IDecorator)RuleDecoratorFactory.createFromNickName(decoratorPair[0], decoratorPair[0], decoratorPair[1], ruleInfo);
					if(d.IsStandin && !(d is IRuleDecorator))
					{
						m_app.Log.write("Unable to define decorator named '{0}' for rule named '{0}' due to decorator claiming to be a rule standin, but does not implement the required IRuleDecorator interface.", d.Name, ruleName);
					}
					else
						ruleInfo.addDecorator(d);
				}
				addRuleDef(ruleName,ruleInfo, rules);
				//rules[ruleName] = ruleInfo;
			}
			catch(Exception e)
			{
				m_app.Log.write("Unable to define business rule '{0}' due to exception:\n{1}", ruleName, e);

				//Fix exception handling
				throw;
			}
		}

		internal void defineCompositeMonitor(string className)
		{
			m_monitors = (ICompositeMonitor)Cmn.callCtor(className);
		}
		internal void defineRuleContextType(string className)
		{
			s_ruleContextType = className;
		}
		#region IRuleManagerEx Members

		public IRunningRule CurrentThreadRunningRule
		{
			get
			{
				return s_ruleQueue.CurrentThreadRunningRule;
			}
		}

		public IRuleInfo getRuleInfo(string ruleName)
		{
			return ruleInfo(ruleName);
		}
		public IRuleInfo getRuleInfo(string name, bool throwException)
		{
			return ruleInfo(name, throwException);
		}
		#endregion
		private class AnonymousClassConfigHandler:ConfigHandler
		{
			protected static string[] s_legalAttributes = new string[]{"class"};
			public AnonymousClassConfigHandler(RuleManager enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				IApplication app = (IApplication)configInfo.Context["app"];
				configInfo.checkIllegalAttributes(s_legalAttributes);
				string className = configInfo.getAttribute("class");
				try
				{
					XmlElement el = configInfo.Element;
					XmlElement queueParams = Cmn.findChildWithName(el, "QueueParams");
					enclosingInstance.defineQueueClass(className, queueParams);
					Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,"Read configuration entry for RuleManager Rule Queue Class {0}", className);
				}
				catch (Exception e)
				{
					xmlError(configInfo.Context, app.resMgr.getMessage("BusinessRules.Threading.BadClassDefinition", className), e);
				}
			}
			private RuleManager enclosingInstance;
		}

		private class AnonymousClassConfigHandler1:ConfigHandler
		{
			protected static string[] s_legalAttributes = new string[]{"name","disabled"};
			public AnonymousClassConfigHandler1(RuleManager enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				IApplication app = (IApplication)configInfo.Context["app"];
				//configInfo.checkIllegalAttributes(s_legalAttributes);
				IList decorators = configInfo.checkUnknownAttributes(s_legalAttributes);
				if (! decoratorExists(decorators,SECURITY_DECORATOR))
					decorators.Add(new string[]{SECURITY_DECORATOR,"enabled"});

				string ruleName = configInfo.getAttribute("name");
				bool disabled = bool.Parse(configInfo.getAttribute("disabled", "false"));
				//Debug.Assert(configInfo is XmlElementConfigElement);
				XmlElement el = configInfo.Element;
				XmlElement typeNode = null;
				foreach(XmlNode cnode in el.ChildNodes)
					if(cnode is XmlElement)
					{
						typeNode = (XmlElement)cnode;
						break;
					}
				if (typeNode == null && !disabled)
					xmlError(configInfo.Context, app.resMgr.getMessage("BusinessRules.Rules.BadRuleDefinition", ruleName));
				else
				{
					enclosingInstance.defineBusinessRule(ruleName, disabled?null:typeNode, decorators);
					Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,
						"	Defined Business Rule {0}, typeInfo={1}, decorators=({2})",
						ruleName,typeNode==null?"":typeNode.OuterXml, decoratorString(decorators));
				}
			}
			private RuleManager enclosingInstance;
			private string decoratorString(IList decorators)
			{
				System.Text.StringBuilder buffer = new System.Text.StringBuilder();
				foreach(string[] entry in decorators)
				{
					buffer.AppendFormat(" {0}={1} ",entry[0],entry[1]);
				}
				return buffer.ToString();
			}

			private bool decoratorExists(IList decorators, string decoratorName)
			{
				bool exists = false;
				foreach(string[] entry in decorators)
				{
					if (entry[0].Equals(decoratorName))
					{
						exists = true;
						break;
					}
				}
				return exists;
			}
		}
		
		private class CompositeMonitorConfigHandler : ConfigHandler
		{
			protected static string[] s_legalAttributes = new string[]{"class"};
			public CompositeMonitorConfigHandler(RuleManager enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				IApplication app = (IApplication)configInfo.Context["app"];
				configInfo.checkIllegalAttributes(s_legalAttributes);
				string className = configInfo.getAttribute("class");
				enclosingInstance.defineCompositeMonitor(className);
				Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,"Read configuration entry for Rule Manager Composite Monitor {0}", className);
			}
			private RuleManager enclosingInstance;
		}

		private class RuleContextConfigHandler : ConfigHandler
		{
			protected static string[] s_legalAttributes = new string[]{"class"};
			public RuleContextConfigHandler(RuleManager enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				IApplication app = (IApplication)configInfo.Context["app"];
				configInfo.checkIllegalAttributes(s_legalAttributes);
				string className = configInfo.getAttribute("class");
				enclosingInstance.defineRuleContextType(className);
				Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,"Read configuration entry for Rule Context Type {0}", className);
			}
			private RuleManager enclosingInstance;
		}
		/// <summary>
		/// Configuration handler to configure ICall that will dynamically load rule 
		/// definitions.
		/// </summary>
		private class DynamicRuleRegistrationHandler : ConfigHandler
		{
			private RuleManager enclosingInstance;

			public DynamicRuleRegistrationHandler(RuleManager enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				IApplication app = (IApplication)configInfo.Context["app"];
				IClassFactory m_callFactory = Application.currentApp.factories["CallTypes"];
				foreach(XmlNode node in configInfo.Element.ChildNodes)
				{
					if (node is XmlElement )
					{
						enclosingInstance.dynamicRuleRegister=(ICall) m_callFactory.createFromNickName(node.Name, "DynamicRuleRegister", (XmlElement) node);						
						Application.currentApp.Log.println(Application.NORMAL_STARTUP_LOG_LEVEL,"DynamicRuleRegistration enabled to use ICall {0}", node.OuterXml);
						break;
					}
				}
			}
		}

        private class RuleGroupConfigHandler : ConfigHandler
		{
			private RuleManager enclosingInstance;
			protected static string[] s_legalAttributes = new string[]{"httpPort", "tcpPort", "name", "visibility"};

			public RuleGroupConfigHandler(RuleManager enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
			//    IApplication app = (IApplication)configInfo.Context["app"];
			//    configInfo.checkIllegalAttributes(s_legalAttributes);
			//    string httpPort = configInfo.getAttribute("httpPort",null);
			//    string tcpPort = configInfo.getAttribute("tcpPort",null);
			    string name = configInfo.getAttribute("name","");
				//TODO: Do something with visibility attribute to make it meaningful.
				string visibility = configInfo.getAttribute("visibility", "public");
				
				Application.currentApp.Log.println(Application.NORMAL_STARTUP_LOG_LEVEL,"Rule Group ({0}) was defined with a visibility of {1}", name, visibility);

				//if(name != "" && (httpPort != null || tcpPort != null))
				//{
				//    RuleManagerServer rms = new RuleManagerServer(name, httpPort, tcpPort);
				//    configInfo.Context["rms"] = rms;
				//    if (! tcpPort.Equals(""))
				//        Application.currentApp.Log.println(Application.NORMAL_STARTUP_LOG_LEVEL,"Rule Group ({0}) is exposed via Remoting via TCP on Port {1}", name, tcpPort);
				//    if (! httpPort.Equals(""))
				//        Application.currentApp.Log.println(Application.NORMAL_STARTUP_LOG_LEVEL,"Rule Group ({0}) is exposed via Remoting via HTTP on Port {1}", name, httpPort);
				//}
			}

			public override void executeAfter(IConfigElement configInfo)
			{
				configInfo.Context.Remove("rms");
			}
		}
	}
}